/*
 * Copyright (c) 2009 Dave Ray <daveray@gmail.com>
 */
package org.jsoar.kernel.commands;

import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;

import org.jsoar.kernel.SoarException;
import org.jsoar.kernel.tracing.Trace;
import org.jsoar.kernel.tracing.Trace.Category;
import org.jsoar.util.commands.SoarCommand;
import org.jsoar.util.commands.SoarCommandContext;

/**
 * Implementation of the "watch" command.
 * 
 * @author ray
 */
public final class WatchCommand implements SoarCommand
{
    private static final Map<String, EnumSet<Category>> categories = new HashMap<String, EnumSet<Category>>();
    
    private static void category(EnumSet<Category> c, String longOpt, String shortOpt)
    {
        if(shortOpt != null) categories.put(shortOpt, c);
        if(longOpt != null) categories.put(longOpt, c);
    }
    
    private static void category(Category c, String longOpt)
    {
        category(EnumSet.of(c), longOpt, longOpt.substring(0, 1));
    }
    private static void category(Category c, String longOpt, String shortOpt)
    {
        category(EnumSet.of(c), longOpt, shortOpt);
    }
    
    static
    {
        category(Category.BACKTRACING, "backtracing");
        category(Category.CONTEXT_DECISIONS, "decisions");
        category(Category.FIRINGS_OF_CHUNKS, "chunks");
        category(Category.FIRINGS_OF_DEFAULT_PRODS, "default", "D");
        category(Category.FIRINGS_OF_JUSTIFICATIONS, "justifications");
        category(Category.FIRINGS_OF_TEMPLATES, "template", "T");
        category(Category.FIRINGS_OF_USER_PRODS, "user");
        category(Category.FIRINGS_PREFERENCES, "preferences", "r");
        category(Category.GDS, "gds");
        category(Category.INDIFFERENT, "indifferent-selection");
        category(Category.PHASES, "phases");
        category(Category.RL, "rl", "R");
        category(Category.VERBOSE, "verbose");
        category(Category.WM_CHANGES, "wmes");
        category(Category.WATERFALL, "waterfall", "W");
        category(EnumSet.of(Category.FIRINGS_OF_USER_PRODS, Category.FIRINGS_OF_JUSTIFICATIONS, Category.FIRINGS_OF_CHUNKS), "productions", "P");
    }
    
    private final Trace trace;
    

    public WatchCommand(Trace trace)
    {
        this.trace = trace;
    }

    
    @Override
    public String execute(SoarCommandContext commandContext, String[] args) throws SoarException
    {
        if(args.length == 1)
        {
            return printWatchSettings();
        }
        
        for(int i = 1; i < args.length; ++i)
        {
            final String arg = args[i];
            if(arg.equals("-N") || arg.endsWith("--none"))
            {
                trace.setWatchLevel(0);
            }
            else if(arg.equals("-l") || arg.equals("--level"))
            {
                if(i + 1 == args.length)
                {
                    throw new SoarException("Missing argument for " + arg + " option.");
                }
                i++;
                processLevel(args[i]);
            }
            else if(arg.startsWith("--"))
            {
                i = processOption(arg.substring(2), i, args);
            }
            else if(arg.startsWith("-"))
            {
                i = processShortOptions(arg, i, args);
            }
            else
            {
                processLevel(arg);
            }
        }
        return "";
    }
    private String printWatchSettings()
    {
        final StringBuilder b = new StringBuilder("Current watch settings:\n");
        b.append(setting("Decisions", Category.CONTEXT_DECISIONS));
        b.append(setting("Phases", Category.PHASES));
        b.append(setting("Default productions", Category.FIRINGS_OF_DEFAULT_PRODS));
        b.append(setting("User productions", Category.FIRINGS_OF_USER_PRODS));
        b.append(setting("Chunks", Category.FIRINGS_OF_CHUNKS));
        b.append(setting("Justifications", Category.FIRINGS_OF_JUSTIFICATIONS));
        b.append(setting("Templates", Category.FIRINGS_OF_TEMPLATES));
        b.append("    WME detail level: " + trace.getWmeTraceType() + "\n");
        b.append(setting("Working memory changes", Category.WM_CHANGES));
        b.append(setting("Preferences generated by firings/retractions", Category.FIRINGS_PREFERENCES));
        b.append("  Learning: ??\n");
        b.append(setting("Backtracing", Category.BACKTRACING));
        b.append(setting("Indifferent selection", Category.INDIFFERENT));
        b.append(setting("Soar-RL", Category.RL));
        b.append(setting("Waterfall", Category.WATERFALL));

        return b.toString();
    }

    private String setting(String title, Category c)
    {
        return "  " + title + ": " + (trace.isEnabled(c) ? "on" : "off") + "\n";
    }
    private int processShortOptions(String arg, int i, String[] args) throws SoarException
    {
        boolean advance = false;
        for(int j = 1; j < arg.length(); ++j)
        {
            final char o = arg.charAt(j);
            int result = processOption(Character.toString(o), i, args);
            advance |= result != i;
        }
        return advance ? i + 1 : i;
    }
    
    private int processOption(String option, int i, String[] args) throws SoarException
    {
        final EnumSet<Category> cats = categories.get(option);
        if(cats == null)
        {
            throw new SoarException("Unsupported option '" + option + "'");
        }
        
        final boolean enabled;
        final int result;
        if(i + 1 < args.length && (args[i+1].equals("remove") || args[i+1].equals("0")))
        {
            enabled = false;
            result = i + 1;
        }
        else if(i + 1 < args.length && !args[i+1].startsWith("-"))
        {
            throw new SoarException("Invalid argument for option '" + option + "'. Expected 'remove' or '0', got '" + args[i+1] + "'");
        }
        else
        {
            enabled = true;
            result = i;
        }
        
        for(Category c : cats)
        {
            trace.setEnabled(c, enabled);
        }
        
        return result;
    }
    
    private void processLevel(String arg) throws SoarException
    {
        try
        {
            trace.setWatchLevel(Integer.valueOf(arg));
        }
        catch(NumberFormatException e)
        {
            throw new SoarException(arg + " is not a valid number");
        }
        catch(IllegalArgumentException e)
        {
            throw new SoarException(e.getMessage());
        }
    }
}